//******************************************************************************
//this code is protected by the GNU affero GPLv3
//author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
//                        <digital.ragnarok AT gmail dot com>
//******************************************************************************

//------------------------------------------------------------------------------
//compiler stuff
//------------------------------------------------------------------------------
#include <stdarg.h>
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//ulinux stuff
//------------------------------------------------------------------------------
#include <ulinux/compiler_types.h>
#include <ulinux/types.h>
#include <ulinux/sysc.h>
#include <ulinux/error.h>
#include <ulinux/args_env.h>

#include <ulinux/file.h>
#include <ulinux/fcntl.h>
#include <ulinux/socket/socket.h>
#include <ulinux/socket/un.h>
#include <ulinux/socket/msg.h>
#include <ulinux/signal/signal.h>
#include <ulinux/mmap.h>
#include <ulinux/epoll.h>
//#include <ulinux/time.h>

#include <ulinux/utils/ascii/ascii.h>
#include <ulinux/utils/ascii/string/string.h>
#include <ulinux/utils/ascii/string/vsprintf.h>
#include <ulinux/utils/mem.h>
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//protocol
//------------------------------------------------------------------------------
#include <wayland/display.h>
#include <wayland/callback.h>
#include <wayland/compositor.h>
#include <wayland/registry.h>
#include <wayland/shm.h>
#include <wayland/shm_pool.h>
#include <wayland/buffer.h>
#include <wayland/seat.h>
#include <wayland/output.h>
#include <wayland/keyboard.h>
#include <wayland/pointer.h>
#define WAYLAND_C
#include <wayland/wayland.h>
#undef WAYLAND_C
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//our private stuff
//------------------------------------------------------------------------------
#define CLIENT_C
#include "globals.h"
#undef CLIENT_C
#include "wire/wl_callback.h"
#include "objs.h"
#include "srv/display.h"
#include "srv/registry.h"
#include "callbacks.h"
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//server stuff
//------------------------------------------------------------------------------
#define SIGBIT(sig) (1<<(sig-1))
static k_i sigs_fd;//server *non blocking* signal fd
static k_i ep_fd;
static struct k_sockaddr_un srv_addr={
  .sun_family=K_AF_UNIX,
  .sun_path[0]=0
};
//------------------------------------------------------------------------------

static void sigs_setup(void)
{
  //cannot change SIGKILL, neither SIGSTOP (the one guilty for K_EINTR)
  k_ul mask=(~0);
  k_l r=sysc(rt_sigprocmask,4,K_SIG_BLOCK,&mask,0,sizeof(mask));
  if(K_ISERR(r)){
    PERR("FATAL(%ld):unable to block mostly all signals\n",r);
    sysc(exit_group,1,-1);
  }

  mask=SIGBIT(K_SIGTERM);
  sigs_fd=(k_i)sysc(signalfd4,4,-1,&mask,sizeof(mask),K_SFD_NONBLOCK);
  if(K_ISERR(sigs_fd)){
    PERR("FATAL(%d):error setting up fd for signals\n",sigs_fd);
    sysc(exit_group,1,-1);
  }
}

static k_u8 *getenv(const char *c,k_i argc,k_u8 **vp)
{
  vp+=argc+1;
  while(*vp){
    k_u8 *equal=*vp;
    while(*equal++!='=');
    if(!u_a_strncmp((k_u8*)c,*vp,equal-1-*vp)) return equal;
    ++vp;
  }
  return 0;
}

static void srv_connect(k_i argc,k_u8 **argv_envp)
{
  srv_so=(k_i)sysc(socket,3,K_PF_UNIX,K_SOCK_STREAM|K_SO_CLOEXEC,0);
  if(K_ISERR(srv_so)){
    PERR("FATAL(%d):unable to create server socket\n",srv_so);
    sysc(exit_group,1,-1);
  }

  k_u8 *xdg_dir=getenv("XDG_RUNTIME_DIR",argc,argv_envp);
  if(!xdg_dir){
    PERRC("FATAL:missing environment variable XDG_RUNTIME_DIR\n");
    sysc(exit_group,1,-1);
  }

  k_u8 *wl_disp=getenv("WAYLAND_DISPLAY",argc,argv_envp);
  if(!wl_disp){
    PERRC("FATAL:missing environment variable WAYLAND_DISPLAY\n");
    sysc(exit_group,1,-1);
  }

  //XXX:only ASCII compatible charsets for names
  k_ul len=u_a_snprintf(&srv_addr.sun_path[0],K_UNIX_PATH_MAX,(k_u8*)"%s/%s",
                                                               xdg_dir,wl_disp);
  if(len==K_UNIX_PATH_MAX){
    PERR("FATAL:%s/%s too big for a unix socket name\n",xdg_dir,wl_disp);
    sysc(exit_group,1,-1);
  }

  k_l r=sysc(connect,3,srv_so,&srv_addr,sizeof(srv_addr));
  if(K_ISERR(r)){
    PERR("FATAL(%ld):unable to to connect to server %s\n",r, srv_addr.sun_path);
    sysc(exit_group,1,-1);
  }
}

static void epoll_setup(void)
{
  ep_fd=(k_i)sysc(epoll_create1,1,K_EPOLL_CLOEXEC);
  if(K_ISERR(ep_fd)){
    PERR("FATAL(%d):unable to create epoll fd\n",ep_fd);
    sysc(exit_group,1,-1);
  }

  struct k_epoll_event ep_evt;

  u_memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=K_EPOLLET|K_EPOLLIN;//edge triggered
  ep_evt.data.fd=sigs_fd;
  k_l r=sysc(epoll_ctl,4,ep_fd,K_EPOLL_CTL_ADD,sigs_fd,&ep_evt);
  if(K_ISERR(r)){
    PERR("FATAL(%ld):unable to register signal fd to epoll\n",r);
    sysc(exit_group,1,-1);
  }

  u_memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=K_EPOLLIN;//level triggered, only read events
  ep_evt.data.fd=srv_so;

  r=sysc(epoll_ctl,4,ep_fd,K_EPOLL_CTL_ADD,srv_so,&ep_evt);
  if(K_ISERR(r)){
    PERR("FATAL(%ld):unable to register server socket to epoll\n",r);
    sysc(exit_group,1,-1);
  }
}

static struct k_msg_hdr msg;//msg from the server
#define MSG_BUF_SZ K_PAGE_SZ//get a single cpu page
static k_u8 msg_buf[MSG_BUF_SZ];
//the control message buffer: control hdr long padded block, plus data long
//padded block
//static k_u8 cmsg_buf[K_CMSG_SPACE(sizeof(k_i))];//may have a file descriptor

static void msg_demux(k_u msg_dws)
{
  k_u32 *msg_start=(k_u32*)&msg_buf[0];
  k_u32 *ev=msg_start;

  do{
    if(ev+1>=msg_start+msg_dws){//from here the event has at least 2 dws
      PERRC("FATAL:event cross message boundaries\n");
      sysc(exit_group,1,-1);
    }
    k_u16 ev_sz=ev[1]>>16;

    if(ev_sz&0x3){
      PERRC("FATAL:event has a payload size not 32 bits word aligned from"
                                                                   " server\n");
      sysc(exit_group,1,-1);
    }

    k_u16 ev_dws=ev_sz>>2;
    if(ev_dws<2||ev+ev_dws>msg_start+msg_dws){
      PERRC("FATAL:event cross message boundaries or is not at least 2 dws\n");
      sysc(exit_group,1,-1);
    }

    objs_ev(ev[0],ev);//event is at least 2 dws and in memory

    ev+=ev_dws;
  }while(ev<msg_start+msg_dws);
}

static void srv_rd(void)
{
  u_memset(&msg,0,sizeof(msg));

  //the request payload buffer
  struct k_io_vec iov;
  iov.base=msg_buf;
  iov.len=MSG_BUF_SZ;
  msg.iov=&iov;
  msg.iov_len=1;
 
  k_l r;
  do r=sysc(recvmsg,3,srv_so,&msg,0);//server *blocking* socket
  while(r==-K_EINTR);

  if(K_ISERR(r)){
    PERR("FATAL(%ld):error receiving msg from server\n",r);
    sysc(exit_group,1,-1);
  }

  if(r&0x3){
    PERRC("FATAL:error receiving msg has a payload size not 32 bits word"
                                                      " aligned from server\n");
    sysc(exit_group,1,-1);
  }

  if(!r) sysc(exit_group,1,0);//server gone

  msg_demux((k_u)(r>>2)); 
}

static void sigs_consume(void)
{
  static struct k_signalfd_siginfo info;

  while(1){
    k_l r;
    do{
      u_memset(&info,0,sizeof(info));
      r=sysc(read,3,sigs_fd,&info,sizeof(info));
    }while(r==-K_EINTR);
    if(r!=-K_EAGAIN&&((K_ISERR(r)||(r>0&&r!=sizeof(info))))){
      PERR("FATAL(%ld):error consuming signals\n",r);
      sysc(exit_group,1,-1);
    }
    if(r==0||r==-K_EAGAIN) break;

    switch(info.ssi_signo){
    case K_SIGTERM:
      sysc(exit_group,1,0);
    }
  }
}

#define EVTS_N 2//kind of arbitrary
static void loop(void)
{
  while(1){
    static struct k_epoll_event evts[EVTS_N];
    k_l r;
    do{
      u_memset(evts,0,sizeof(evts));
      r=sysc(epoll_wait,4,ep_fd,evts,EVTS_N,-1);
    }while(r==-K_EINTR);
    if(K_ISERR(r)){
      PERR("FATAL(%ld):error epolling fds\n",r);
      sysc(exit_group,1,-1);
    }
    for(k_l i=0;i<r;++i)
      if(evts[i].data.fd==sigs_fd){
        if(evts[i].events&K_EPOLLIN) sigs_consume();
        else{
          PERR("FATAL:something wrong with signal fd epolling n=%ld"
                                               " events=%u\n",i,evts[i].events);
          sysc(exit_group,1,-1);
        }
      }else if(evts[i].data.fd==srv_so){
        if(evts[i].events&(K_EPOLLERR)){
          PERR("FATAL:something wrong with server socket epolling n=%ld"
                                               " events=%u\n",i,evts[i].events);
          sysc(exit_group,1,-1);
        }else if(evts[i].events&(K_EPOLLHUP)) sysc(exit_group,1,0);//server gone
        else if(evts[i].events&K_EPOLLIN) srv_rd();
        else{
          PERR("FATAL:unknown epolling event on server socket n=%ld"
                                               " events=%u\n",i,evts[i].events);
          sysc(exit_group,1,-1);
        }
      }
  }
}

//******************************************************************************
void start(k_i argc, k_u8 **argv_envp)
{
  static k_u8 dprintf_buf[DPRINTF_BUF_SZ];
  g_dprintf_buf=dprintf_buf;

  objs_init();
  registry_init();
  sigs_setup();
  srv_connect(argc,argv_envp);
  epoll_setup();
  registry_get();
  PERRC("syncing for shm binding...\n");
  display_sync(callback_shm_binding);
  loop();
  sysc(exit_group,1,0);
}
